﻿using System.Reflection;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace ProxyViewModel {
	/// <summary>
	/// Gets the information needed from a specific System.Type
	/// </summary>
	public class ReflectionHelper {
		private static readonly BindingFlags Public = BindingFlags.Public | BindingFlags.Instance;
		private static readonly BindingFlags PublicProtectedPrivate = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;

		internal static IList<PropertyInfo> GetPublicVirtualProperties(Type inputType) {
			var propertiesCollection = inputType.GetProperties(Public);
			return propertiesCollection.Where(e => CanOverrideProperty(e)).ToArray();
		}

		internal static bool CanOverrideProperty(PropertyInfo info) {
			return info.GetSetMethod() != null && info.GetSetMethod().IsVirtual;
		}

		internal static IList<MethodInfo> GetMethodsMarkedWithAttribute<Attribute>(Type inputType) {
			return inputType.GetMethods(PublicProtectedPrivate).Where(e => e.GetCustomAttributes(true).Select(a => a.GetType()).Contains(typeof(Attribute))).
				Select(e => e).ToList();
		}

		internal static MethodInfo GetMethodByName(Type inputType, string methodName) {
			MethodInfo info = inputType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			Type currentType = inputType.BaseType;
			while (info == null && currentType != null) {
				info = currentType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
				currentType = currentType.BaseType;
			}
			return info;
		}

		internal static MethodInfo GetMethod(Type inputType, string name, params Type[] parameters) {
			foreach (MethodInfo method in inputType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
				if (name == method.Name) {
					Type[] parameterTypes = method.GetParameters().Select(e => e.ParameterType).ToArray();

					if (parameterTypes.Length != parameters.Length) {
						continue;
					}

					bool eqauls = true;
					for (int i = 0; i < parameterTypes.Length; ++i) {
						if (parameterTypes[i] != parameters[i]) {
							eqauls = false;
						}
					}

					if (eqauls) {
						return method;
					}
				}
			}
			return null;
		}

		internal static MethodInfo[] GetMethodsByNameMatch(Type inputType, string regexExpr) {
			Regex regex = new Regex(regexExpr);
			return inputType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(e => regex.IsMatch(e.Name)).ToArray();
		}

		internal static bool MethodSignatureEquals(MethodInfo info1, MethodInfo info2) {
			if (info1.GetParameters().Length != info2.GetParameters().Length) {
				return false;
			}

			for (int i = 0; i < info1.GetParameters().Length; ++i) {
				if (info1.GetParameters()[i].ParameterType !=
					info2.GetParameters()[i].ParameterType) {
					return false;
				}
			}

			return true;
		}

		public static void RaisePropertyChangedEvent(string propertyName, object targetObject) {
			FieldInfo propertyChangedField = null;
			Type currentType = targetObject.GetType();

			while (propertyChangedField == null && currentType != null) {
				propertyChangedField = currentType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
				currentType = currentType.BaseType;
			}

			if (propertyChangedField == null) {
				throw new InvalidOperationException(string.Format("PropertyChanged event was not found for object of '{0}' type", targetObject.GetType().FullName));
			}

			MethodInfo info = typeof(PropertyChangedEventHandler).GetMethod("Invoke", new Type[] { typeof(object), typeof(PropertyChangedEventArgs) });
			info.Invoke(propertyChangedField.GetValue(targetObject), new object[] { targetObject, new PropertyChangedEventArgs(propertyName) });
		}
	}
}
